@file:JvmName("DefaultService")

package edu.hubu.web.service

import edu.hubu.web.commons.*
import edu.hubu.web.commons.EntityStatus.*
import edu.hubu.web.dao.IBaseDao
import edu.hubu.web.exception.InformationNotFoundException
import edu.hubu.web.exception.InternalServerException
import edu.hubu.web.model.BaseEntity
import org.springframework.data.domain.Page
import org.springframework.data.domain.PageRequest
import org.springframework.data.jpa.domain.Specification
import java.io.Serializable
import java.time.LocalDateTime
import java.util.*
import javax.persistence.EntityManager
import javax.persistence.Id
import javax.persistence.Query
import javax.persistence.criteria.*


open class DefaultService<EntityType : BaseEntity?, IdType : Serializable?> {
    val baseDao: IBaseDao<EntityType, IdType>
    private val em: EntityManager
    private var clazz: Class<EntityType>? = null
    var deleteFieldName: String? = null
        private set
    var forbiddenFieldName: String? = null
        private set

    constructor(baseDao: IBaseDao<EntityType, IdType>, clazz: Class<EntityType>, em: EntityManager) {
        this.baseDao = baseDao
        this.clazz = clazz
        this.em = em
    }

    constructor(
        baseDao: IBaseDao<EntityType, IdType>,
        clazz: Class<EntityType>,
        em: EntityManager,
        deleteFieldName: String
    ) {
        this.baseDao = baseDao
        this.clazz = clazz
        this.em = em
        this.deleteFieldName = deleteFieldName
    }

    constructor(
        baseDao: IBaseDao<EntityType, IdType>,
        clazz: Class<EntityType>,
        em: EntityManager,
        deleteFieldName: String,
        forbiddenFieldName: String
    ) {
        this.baseDao = baseDao
        this.clazz = clazz
        this.em = em
        this.deleteFieldName = deleteFieldName
        this.forbiddenFieldName = forbiddenFieldName
    }

    constructor(baseDao: IBaseDao<EntityType, IdType>, em: EntityManager) {
        this.baseDao = baseDao
        this.em = em
    }

    constructor(baseDao: IBaseDao<EntityType, IdType>, em: EntityManager, deleteFieldName: String) {
        this.baseDao = baseDao
        this.em = em
        this.deleteFieldName = deleteFieldName
    }

    constructor(
        baseDao: IBaseDao<EntityType, IdType>,
        em: EntityManager,
        deleteFieldName: String,
        forbiddenFieldName: String
    ) {
        this.baseDao = baseDao
        this.em = em
        this.deleteFieldName = deleteFieldName
        this.forbiddenFieldName = forbiddenFieldName
    }

    /**
     * 立即通过id查询，用于外部接口
     *
     * @param id 实体Id
     * @param predicate 查询对象的筛选条件
     * @return 实体类
     */
    @JvmOverloads
    open fun findEntityById(id: IdType, predicate: (EntityType) -> Boolean = { _: EntityType -> true }): EntityType {
        val byId = baseDao.findById(id)
        if (!byId.isPresent) {
            throw InformationNotFoundException("未找到id=${id}的实体")
        }
        val type = byId.get()
        val filter = ObjectUtils.filter(type!!)
        //比如要求对象的isDelete属性不能为true，而对象的属性就为true，则进入if，如果对象的isDelete属性要为false，而实际上也为false，则不进入if
        if (!predicate.invoke(filter)) {
            throw InformationNotFoundException("未找到id=${id}的实体")
        }
        return filter
    }

    /**
     * 立即通过id查询，用于内部接口
     *
     * @param id 实体Id
     * @param predicate 查询对象的筛选条件
     * @return 实体类
     */
    @JvmOverloads
    open fun getEntityById(id: IdType, predicate: (EntityType) -> Boolean = { _: EntityType -> true }): EntityType {
        val byId = baseDao.findById(id)
        if (!byId.isPresent) {
            throw InformationNotFoundException("未找到id=${id}的实体")
        }
        val type = byId.get()
        //比如要求对象的isDelete属性不能为true，而对象的属性就为true，则进入if，如果对象的isDelete属性要为false，而实际上也为false，则不进入if
        if (!predicate.invoke(type)) {
            throw InformationNotFoundException("未找到id=${id}的实体")
        }
        return type
    }

    /**
     * 根据属性名为fieldName，值为value查询实体
     *
     * @param fieldName 属性名，不可为null
     * @param fieldValue 属性值值，不可为null
     * @param predicate 查询对象的筛选条件
     * @return 实体
     */
    @JvmOverloads
    open fun getEntityByUniqueField(
        fieldName: String,
        fieldValue: Any,
        predicate: (EntityType) -> Boolean = { _: EntityType -> true }
    ): EntityType {
        if (ObjectUtils.isNullOrEmpty(fieldValue)) {
            throw IllegalArgumentException("$fieldValue 为空或为null")
        }
        val one =
            baseDao.findOne { root, _, criteriaBuilder -> criteriaBuilder.equal(root.get<Any>(fieldName), fieldValue) }
        if (!one.isPresent) {
            throw InformationNotFoundException("未找到fieldName=${fieldName}, fieldValue=${fieldValue}的实体")
        }
        val type = one.get()
        if (!predicate.invoke(type)) {
            throw InformationNotFoundException("未找到fieldName=${fieldName}, fieldValue=${fieldValue}的实体")
        }
        return type
    }

    open fun findEntitiesPagedByCol(
        currentPage: Int = 0,
        pageSize: Int = 5,
        colPairs: List<Pair<String, Any>>,
        predicate: (EntityType) -> Boolean = { _: EntityType -> true }
    ): PageData<EntityType> {
        val request = PageRequest.of(currentPage, pageSize)
        val page = baseDao.findAll(Specification { root, criteriaQuery, criteriaBuilder ->
            for (colPair in colPairs) {
                val pre = criteriaBuilder.like(
                    root.get<Any>(colPair.first).`as`(String::class.java),
                    "%${colPair.second}%"
                )
                criteriaQuery.where(pre)
            }
            return@Specification criteriaQuery.restriction
        }, request)
        var ret = page.content.filter(predicate).map { ObjectUtils.filter(it) }
        if (!ObjectUtils.isNullOrEmpty(deleteFieldName)) {
            ret = ret.filter { ObjectUtils.reflectCheckFieldValue(it!!, deleteFieldName!!, false) }
        }

        if (!ObjectUtils.isNullOrEmpty(forbiddenFieldName)) {
            ret = ret.filter { ObjectUtils.reflectCheckFieldValue(it!!, forbiddenFieldName!!, false) }
        }
        return PageData(page.totalPages, page.totalElements.toInt(), currentPage, ret)
    }

    open fun findEntitiesPagedByFK(
        currentPage: Int = 0,
        pageSize: Int = 5,
        fkPairs: List<Triples<String, String, Any>>,
        predicate: (EntityType) -> Boolean = { _: EntityType -> true }
    ): PageData<EntityType> {
        val request = PageRequest.of(currentPage, pageSize)
        val page = baseDao.findAll(Specification { root, criteriaQuery, criteriaBuilder ->
            for (fkPair in fkPairs) {
                val join = root.join<Any, Any>(fkPair.first)
                val fk = criteriaBuilder.like(
                    join.get<Any>(fkPair.second).`as`(String::class.java),
                    "%${fkPair.third}%"
                )
                criteriaQuery.where(fk);
            }
            return@Specification criteriaQuery.restriction
        }, request)
        var ret = page.content.filter(predicate).map { ObjectUtils.filter(it) }
        if (!ObjectUtils.isNullOrEmpty(deleteFieldName)) {
            ret = ret.filter { ObjectUtils.reflectCheckFieldValue(it!!, deleteFieldName!!, false) }
        }

        if (!ObjectUtils.isNullOrEmpty(forbiddenFieldName)) {
            ret = ret.filter { ObjectUtils.reflectCheckFieldValue(it!!, forbiddenFieldName!!, false) }
        }
        return PageData(page.totalPages, page.totalElements.toInt(), currentPage, ret)
    }


    open fun getEntitiesPaged(
        currentPage: Int = 0,
        pageSize: Int = 5,
        tableInfo: TableInfo? = null,
        predicate: (EntityType) -> Boolean = { _: EntityType -> true }
    ): PageData<EntityType> {
        val request = PageRequest.of(currentPage, pageSize)
        val page: Page<EntityType>
        if (ObjectUtils.isNullOrEmpty(tableInfo)) {
            page = baseDao.findAll(request)
        } else {
            page = baseDao.findAll(Specification { root, criteriaQuery, criteriaBuilder ->
                val predicates = ArrayList<Predicate>()
                val pkName = tableInfo!!.pkName
                if (!ObjectUtils.isNullOrEmpty(pkName)) {
                    val pk = criteriaBuilder.equal(
                        root.get<Any>(pkName.first).`as`(String::class.java),
                        "${pkName.second}"
                    )
                    predicates.add(pk)
                }


                val colPairs = tableInfo.normalColumns
                if (!ObjectUtils.isNullOrEmpty(colPairs)) {
                    for (colPair in colPairs) {
                        if (!ObjectUtils.isNullOrEmpty(colPair)) {
                            val col = criteriaBuilder.like(
                                root.get<Any>(colPair.first).`as`(String::class.java),
                                "%${colPair.second}%"
                            )
                            predicates.add(col)
                        }
                    }
                }

                val fkPairs = tableInfo.fkColumns
                if (!ObjectUtils.isNullOrEmpty(fkPairs)) {
                    for (fkPair in fkPairs) {
                        if (!ObjectUtils.isNullOrEmpty(fkPair)) {
                            val join = root.join<Any, Any>(fkPair.first)
                            val fk = criteriaBuilder.like(
                                join.get<Any>(fkPair.second).`as`(String::class.java),
                                "%${fkPair.third}%"
                            )
                            predicates.add(fk)
                        }
                    }
                }

                val array = predicates.toArray(emptyArray<Predicate>())
                return@Specification criteriaQuery.where(*array).restriction
            }, request)
        }

        var ret = page.content.filter(predicate).map { ObjectUtils.filter(it) }
        if (!ObjectUtils.isNullOrEmpty(deleteFieldName)) {
            ret = ret.filter { ObjectUtils.reflectCheckFieldValue(it!!, deleteFieldName!!, false) }
        }

        if (!ObjectUtils.isNullOrEmpty(forbiddenFieldName)) {
            ret = ret.filter { ObjectUtils.reflectCheckFieldValue(it!!, forbiddenFieldName!!, false) }
        }
        return PageData(page.totalPages, page.totalElements.toInt(), currentPage, ret)
    }

    @JvmOverloads
    @Deprecated("替换为getEntitiesPaged")
    fun findEntitiesPaged(
        currentPage: Int = 0,
        pageSize: Int = 5,
        entityType: EntityType? = null,
        predicate: (EntityType) -> Boolean = { _: EntityType -> true }
    ): PageData<EntityType> {
        val request = PageRequest.of(currentPage, pageSize)
        val all: Page<EntityType>
        val map = ObjectUtils.toMap(entityType)
        all = baseDao.findAll(Specification { root, criteriaQuery, criteriaBuilder ->
            val predicates = ArrayList<Predicate>()
            for (entry in map) {
                if (entry.value is Boolean) {
                    predicates.add(criteriaBuilder.equal(root.get<Any>(entry.key), entry.value))
                    continue
                }

                if (entry.value is Number || entry.value is String) {
                    predicates.add(
                        criteriaBuilder.like(
                            root.get<Any>(entry.key).`as`(String::class.java),
                            "%$entry.value%"
                        )
                    )
                    continue
                }

                if (entry.value is Date || entry.value is LocalDateTime || entry.value is Calendar) {
                    continue
                }

                if (entry.value is BaseEntity) {
                    val join = root.join<Any, Any>(entry.key)
                    val filter = ObjectUtils.toMap(entry.value)
                    for ((key, newValue) in filter) {
                        addPredicates(join, criteriaQuery, criteriaBuilder, predicates, key, newValue)
                    }
                }
            }

            for (p in predicates) {
                criteriaQuery.where(p)
            }

            return@Specification criteriaQuery.restriction
        }, request)
        var ret = all.content.filter(predicate).map { ObjectUtils.filter(it) }
        if (!ObjectUtils.isNullOrEmpty(deleteFieldName)) {
            ret = ret.filter { ObjectUtils.reflectCheckFieldValue(it!!, deleteFieldName!!, false) }
        }

        if (!ObjectUtils.isNullOrEmpty(forbiddenFieldName)) {
            ret = ret.filter { ObjectUtils.reflectCheckFieldValue(it!!, forbiddenFieldName!!, false) }
        }
        return PageData(all.totalPages, all.totalElements.toInt(), currentPage, ret)

    }


    /**
     * 添加实体，如果有外键，只能填写外键类的ID属性
     *
     * @param entityType 需要被添加的实体，不可为null
     * @return 添加成功的实体对象
     */
    open fun addEntity(entityType: EntityType): EntityType {
        try {
            val idField =
                entityType!!::class.java.declaredFields.first { field -> field.isAnnotationPresent(Id::class.java) }
            val value = ObjectUtils.reflectGetFieldValue(entityType, idField.name)
            if (ObjectUtils.isNullOrEmpty(value)) {
                ObjectUtils.reflectSetFieldValue(
                    entityType,
                    idField.name,
                    entityType.getUUID(clazz!!.simpleName, UUID.randomUUID().toString().substring(0, 8))
                )
            }
            val save = baseDao.save(entityType)
            return ObjectUtils.filter(save)
        } catch (exp: Exception) {
            throw RuntimeException("添加entity : " + entityType.toString() + "失败,原因为$exp")
        }
    }

    /**
     * 根据ID更新实体对象
     *
     * @param id     不可为null
     * @param entityType 不可为null
     * @return 更新后的实体对象
     */
    fun updateEntityById(id: IdType, entityType: EntityType): EntityType {
        val byId = getEntityById(id)
        ObjectUtils.assign(entityType!!, byId!!)
        val save = baseDao.saveAndFlush(byId)
        return ObjectUtils.filter(save)
    }

    open fun deleteEntityById(id: IdType): Boolean {
        try {
            baseDao.deleteById(id)
            return true
        } catch (e: Exception) {
            throw InternalServerException("找不到id=${id}的实体,原因为$e")
        }
    }

    open fun logicDeleteEntityById(id: IdType): Boolean {
        try {
            val byId = getEntityById(id)
            if (ObjectUtils.reflectCheckFieldValue(byId!!, deleteFieldName!!, true)) {
                throw InternalServerException("找不到id=${id}的实体")
            }
            ObjectUtils.reflectSetFieldValue(byId, deleteFieldName!!, true)
            baseDao.saveAndFlush(byId)
            return true
        } catch (e: Exception) {
            throw InternalServerException("逻辑删除id=" + id + "对象失败,原因为$e")
        }
    }

    open fun forbidEntityById(id: IdType): Boolean {
        try {
            val byId = getEntityById(id)
            if (ObjectUtils.reflectCheckFieldValue(byId!!, deleteFieldName!!, true) ||
                ObjectUtils.reflectCheckFieldValue(byId, forbiddenFieldName!!, true)
            ) {
                throw InternalServerException("找不到id=${id}的实体")
            }
            ObjectUtils.reflectSetFieldValue(byId, forbiddenFieldName!!, true)
            baseDao.saveAndFlush(byId)
            return true
        } catch (e: Exception) {
            throw InternalServerException("封禁id=" + id + "对象失败,原因为$e")
        }
    }

    /**
     * 查询数量
     *
     * @return 数量
     */
    open fun getCount(): Int {
        val count = baseDao.count { root, query, criteriaBuilder ->
            val list = ArrayList<Predicate>()
            if (!ObjectUtils.isNullOrEmpty(deleteFieldName)) {
                list.add(criteriaBuilder.equal(root.get<Any>(deleteFieldName), false))
            }
            if (!ObjectUtils.isNullOrEmpty(forbiddenFieldName)) {
                list.add(criteriaBuilder.equal(root.get<Any>(forbiddenFieldName), false))
            }
            return@count query.where(*list.toTypedArray()).restriction
        }
        return count.toInt()
    }

    /**
     * 解除封禁
     * @param id
     * @return
     */
    open fun resumeForbiddenEntity(id: IdType): Boolean {
        try {
            val byId = getEntityById(id)
            if (ObjectUtils.reflectCheckFieldValue(byId!!, deleteFieldName!!, false) ||
                ObjectUtils.reflectCheckFieldValue(byId, forbiddenFieldName!!, false)
            ) {
                throw InternalServerException("被封禁的实体中，找不到id=${id}的实体")
            }
            ObjectUtils.reflectSetFieldValue(byId, forbiddenFieldName!!, false)
            baseDao.saveAndFlush(byId)
            return true
        } catch (e: Exception) {
            throw InternalServerException("解除封禁id=" + id + "对象失败,原因为$e")
        }
    }

    /**
     * 解除删除
     *
     * @param id
     * @return
     */
    open fun resumeDeletedEntity(id: IdType): Boolean {
        try {
            val byId = getEntityById(id)
            if (ObjectUtils.reflectCheckFieldValue(byId!!, deleteFieldName!!, false)) {
                throw InternalServerException("被删除的找不到id=${id}的实体")
            }
            ObjectUtils.reflectSetFieldValue(byId, deleteFieldName!!, false)
            baseDao.saveAndFlush(byId)
            return true
        } catch (e: Exception) {
            throw InternalServerException("解除封禁id=" + id + "对象失败,原因为$e")
        }
    }

    open fun updateEntityStatusById(id: IdType, entityStatus: EntityStatus): Boolean {
        try {
            val byId = getEntityById(id)
            when (entityStatus) {
                NOT_FORBIDDEN -> {
                    ObjectUtils.reflectSetFieldValue(byId!!, forbiddenFieldName!!, false)
                }
                NOT_DELETED -> {
                    ObjectUtils.reflectSetFieldValue(byId!!, deleteFieldName!!, false)
                }
                FORBIDDEN -> {
                    ObjectUtils.reflectSetFieldValue(byId!!, forbiddenFieldName!!, true)
                }
                DELETED -> {
                    ObjectUtils.reflectSetFieldValue(byId!!, deleteFieldName!!, true)
                }
            }
            baseDao.saveAndFlush(byId)
            return true
        } catch (e: Exception) {
            throw InternalServerException("更新id=" + id + "实体的状态失败,原因为$e")
        }
    }

    @Deprecated("")
    private fun addPredicates(
        joinTable: Join<Any, Any>,
        criteriaQuery: CriteriaQuery<*>,
        criteriaBuilder: CriteriaBuilder,
        predicates: ArrayList<Predicate>,
        name: String,
        value: Any
    ) {
        if (value is Boolean) {
            predicates.add(criteriaBuilder.equal(joinTable.get<Any>(name), value))
            return
        }

        if (value is Number || value is String) {
            predicates.add(criteriaBuilder.equal(joinTable.get<Any>(name).`as`(String::class.java), value))
            return
        }

        if (value is Date || value is LocalDateTime || value is Calendar) {
            return
        }

        if (value is BaseEntity) {
            val join = joinTable.join<Any, Any>(name)
            val filter = ObjectUtils.toMap(value)
            for ((key, newValue) in filter) {
                addPredicates(join, criteriaQuery, criteriaBuilder, predicates, key, newValue)
            }
        }
    }

    /**
     * 增加第三方关联表的数据
     * @param tableName 关联表表名
     * @param left 左列键值
     * @param right 右列键值
     * @return 是否添加成功
     */
    open fun addRefType(tableName: String, left: Pairs<String, String>, right: Pairs<String, String>): Boolean {
        val query: Query = em.createNativeQuery("insert into $tableName(?, ?) values (?, ?)")
        query.setParameter(1, left.first).setParameter(2, right.first).setParameter(3, left.second)
            .setParameter(4, right.second)
        val update = query.executeUpdate()
        return update > 0
    }

    /**
     * 删除第三张关联表的数据
     * @param tableName 关联表表名
     * @param left 左列键名，键值
     * @param right 左列键名，键值
     * @return 是否删除成功
     */
    open fun delRefType(tableName: String, left: Pairs<String, String>, right: Pairs<String, String>): Boolean {
        val query: Query = em.createNativeQuery("delete from $tableName where ? = ? and ? = ?")
        query.setParameter(1, left.first).setParameter(2, left.second).setParameter(3, right.first)
            .setParameter(4, right.second)
        val update = query.executeUpdate()
        return update > 0
    }

//    fun select(
//        tableName: String,
//        selectColNames: List<String> = emptyList(),
//        params: List<Triples<String, String, Any>> = emptyList()
//    ): List<EntityType> {
//        var cols: String = ""
//        var pars: String = ""
//        if (selectColNames.isNotEmpty()) {
//            val builder = StringBuilder()
//            for (colName in selectColNames) {
//                builder.append(colName).append(",")
//            }
//            cols = builder.deleteAt(builder.length - 1).toString()
//        }
//
//        if (params.isNotEmpty()) {
//            val builder = StringBuilder()
//            for (param in params) {
//                builder.append(param.first).append(param.second).append(param.third).append(" ")
//            }
//            pars = builder.deleteAt(builder.length - 1).toString()
//            val query: Query = em.createNativeQuery("select $cols from $tableName where $pars")
//        }
//
//        val query: Query = em.createNativeQuery("select $cols from $tableName")
//        query.
//    }

}